package com.code.soulnetty.modbus.client.service;

/**
 * 处理CRC16逻辑
 *
 **/
public class CrcUtils {

    private CrcUtils() {
    }

    /**
     * 1. CRC16计算方法
     * <p>
     * 1) 预置 1 个 16 位的寄存器为十六进制FFFF(即全为 1) , 称此寄存器为 CRC寄存器。
     * <p>
     * 2) 把第一个 8 位二进制数据 (通信信息帧的第一个字节) 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器。
     * <p>
     * 3) 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位。
     * <p>
     * 4) 如果移出位为 0, 重复第 3 步 ( 再次右移一位); 如果移出位为 1, CRC 寄存器与多项式A001 ( 1010 0000 0000 0001) 进行异或。
     * <p>
     * 5) 重复步骤 3 和步骤 4, 直到右移 8 次,这样整个8位数据全部进行了处理。
     * <p>
     * 6) 重复步骤 2 到步骤 5, 进行通信信息帧下一个字节的处理。
     * <p>
     * 7) 将该通信信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换。
     * <p>
     * 8) 最后得到的 CRC寄存器内容即为 CRC码。
     * <p>
     * 2：modbus协议
     * <p>
     * 线圈寄存器，实际上就可以类比为开关量，每一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。
     * 线圈寄存器支持读也支持写，写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是：0x01  0x05  0x0f
     * <p>
     * 离散输入寄存器，如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式，他也是每个bit
     * 表示一个开关量，而他的开关量只能读取输入的开关信号，是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02
     * <p>
     * 保持寄存器，这个寄存器的单位不再是bit而是两个byte，也就是可以存放具体的数据量的，并且是可读写的。比如我我设置时间年月日，
     * 不但可以写也可以读出来现在的时间。写也分为单个写和多个写，所以功能码有对应的三个：0x03 0x06 0x10
     * <p>
     * 输入寄存器，只剩下这最后一个了，这个和保持寄存器类似，但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。
     * 类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04
     * </p>
     *
     * @param data
     * @param length
     * @return
     */
    public static byte[] crc16ToByte(byte[] data, int length) {
        int tmp = crc16ToInt(data, length);
        return new byte[]{(byte) (tmp >> 8), (byte) tmp};
    }

    /**
     * 计算crc16
     *
     * @param data
     * @return
     */
    public static byte[] crc16ToByte(byte[] data) {
        int tmp = crc16ToInt(data, data.length);

        return new byte[]{(byte) (tmp >> 8), (byte) tmp};
    }

    /**
     * 计算crc16 结果存为int
     *
     * @param data
     * @param length
     * @return
     */
    public static int crc16ToInt(byte[] data, int length) {
        int tmp = 0xffff;
        for (int n = 0; n < length; n++) {
            tmp = (data[n] & 0xff) ^ tmp;
            for (int i = 0; i < 8; i++) {
                if ((tmp & 0x01) > 0) {
                    tmp = tmp >> 1;
                    tmp = tmp ^ 0xa001;
                } else {
                    tmp = tmp >> 1;
                }
            }
        }
        /*将CRC校验的高低位对换位置*/
        return tmp >> 8 | (tmp << 8 & 0xffff);
    }
}
